import urllib

import pygame

from geometry import RectGeometry, CircleGeometry, PolyGeometry
import quadtree
import settings

class World(object):
    """This class wraps quatree.QuadTree.
    """
    
    def __init__(self, rect):
        """rect is a pygame.Rect or sequence that defines the world bounds:
        0,0,width,height.
        """
        self.rect = pygame.Rect(rect)
        self.quadtree = quadtree.QuadTree(
            rect, collide_entities=settings.precise_collisions)
    
    def step(self, dt):
        pass
    
    def add(self, *entities):
        """Add entities to tree.
        """
        self.add_list(entities)
    
    def add_list(self, entities):
        """Add list of entities to tree.
        """
        self.quadtree.add_list(entities)
    
    def remove(self, *entities):
        """Remove entities from tree.
        """
        self.remove_list(entities)
    
    def remove_list(self, entities):
        """Remove list of entities from tree.
        """
        self.quadtree.remove_list(entities)
    
    def clear(self):
        """Clear all entities from tree.
        """
        self.quadtree.remove_list(self.quadtree.entity_branch.keys())
    
    def get_collisions(self, entity):
        """Return a list of entities that collide with entity.
        """
        collisions = []
        for left,right in self.quadtree.collisions:
            if left is entity:
                collisions.append(right)
        return collisions
    
    def get_collisions_dict(self):
        """Return a dict of collisions. Item is entity:[entities...].
        """
        return self.quadtree.collisions_dict()
    
    def entities(self):
        """Return a list of all entities in the tree.
        """
        return self.quadtree.entity_branch.keys()
    
    def entities_in(self, rect):
        """Return a list of entities that intersect rect.
        """
        return self.quadtree.entities_in(rect)


## NOTE: Tilesheet handling commented out for now. It has a number of
## dependencies.
def load_entities(filepath, cls_dict={}):
    """Load entities via the import_world_quadtree plugin. Return a list of
    entities.
    
    The cls_dict argument is a dict of classes to construct when encountering
    shape data. The following keys are supported: 'rect_cls', 'poly_cls',
    'circle_cls'. If any of those keys are missing from cls_dict, the following
    classes will be used by default: geometry.RectGeometry,
    geometry.PolyGeometry, geometry.CircleGeometry. Classes substituted in this
    manner must have constructors that are compatible with the default classes.
    """
    file_handle = open(filepath, 'rb')
##    entities,tilesheets = import_world_quadtree(
    entities = import_world_quadtree(
        file_handle, RectGeometry, PolyGeometry, CircleGeometry)
    file_handle.close()
    return entities  ##,tilesheets


## NOTE: Tilesheet handling commented out for now. It has a number of
## dependencies.
def import_world_quadtree(fh, rect_cls, poly_cls, circle_cls):
    """A world entity importer compatible with QuadTree.
    
    This function is required by world_editor.py, and possibly other scripts, to
    import world entities from a text file. It understands the format of files
    created by export_world_quadtree().

    Geometry classes used by this function to create shape objects are specified
    by the rect_cls, poly_cls, and circle_cls arguments. The constructor
    parameters must have the same signature as geometry.RectGeometry, et al.

    The values imported are those needed for each shape-class's constructor,
    plus a block of arbitrary user data which will be placed in the shape
    instance's user_data attribute.

    The user_data is also parsed for tilesheet info. Tilesets are loaded and
    returned as a dict of toolkit.Tilesheet, keyed by relative path to the
    image file.
    """
    
    if not issubclass(rect_cls, RectGeometry):
        raise pygame.error, 'argument "rect_cls" must be a subclass of geometry.RectGeometry'
    if not issubclass(poly_cls, PolyGeometry):
        raise pygame.error, 'argument "poly_cls" must be a subclass of geometry.PolyGeometry'
    if not issubclass(circle_cls, CircleGeometry):
        raise pygame.error, 'argument "circle_cls" must be a subclass of geometry.CircleGeometry'
    
    entities = []
    tilesheets = {}
    
    line_num = 0
    for line in fh:
        line_num += 1
        line = line.rstrip('\r\n')
        parts = line.split(' ')
        if len(parts) < 1:
            continue
        what = parts[0]
        if what == 'user_data':
            # User data format:
            # user_data url_encoded_string
            user_data = []
            # Unquote the user_data string and split it into lines.
            lines = urllib.unquote(' '.join(parts[1:])).split('\n')
            # Scan each line for tile info, load the tilesheets, and populate the
            # entity's user_data attribute.
            for line in lines:
                # Split into space-delimited tokens.
                parts = line.split(' ')
                if parts[0] == 'tile':
                    # Process the tile info entry. Format is:
                    # 0: tile
                    # 1: tile_id
                    # 2..end: relpath_of_image
                    file_path = ' '.join(parts[2:])
                    file_path = os.path.join(*file_path.split('/'))
##                    if file_path not in tilesheets:
##                        tilesheet = load_tilesheet(file_path)
##                        tilesheets[file_path] = tilesheet
                    # Join the parts and append to user_data.
                    line = ' '.join(parts[0:2] + [file_path])
                elif parts[0] == 'name':
                    entity.name = parts[1]
                user_data.append(line)
            entity.user_data = '\n'.join(user_data)
        elif what == 'rect':
            # Rect format:
            # rect x y w h
            x,y = int(parts[1]), int(parts[2])
            w,h = int(parts[3]), int(parts[4])
            entity = rect_cls(x, y, w, h)
            entity.name = ''
            entities.append(entity)
        elif what == 'circle':
            # Circle format:
            # circle centerx centery radius
            x,y = int(parts[1]), int(parts[2])
            radius = float(parts[3])
            entity = circle_cls((x,y), radius)
            entity.name = ''
            entities.append(entity)
        elif what == 'poly':
            # Polygon format:
            # poly centerx centery rel_x1 rel_y1 rel_x2 rel_y2 rel_x3 rel_y3...
            #
            # Note: x and y are relative to the containing rect's topleft.
            center = int(parts[1]), int(parts[2])
            points = []
            for i in range(3, len(parts), 2):
                points.append(( int(parts[i]), int(parts[i+1]) ))
            entity = poly_cls(points, center)
            entity.name = ''
            entities.append(entity)
        else:
            raise pygame.error, 'line %d: keyword "%s" unexpected' % (line_num,what)
        
    return entities  ##, tilesheets
